Skip to main content

Reusable Sticky Notes Component

A fully-featured, drag-and-drop sticky notes component for React applications with TypeScript support. Features auto-save, rich text editing, customizable colors, and server persistence.

Source Code

Gitlab Repository

✨ Features

  • 🎯 Drag & Drop - Move notes anywhere on screen
  • 📏 Resizable - Adjust note size with corner/edge handles
  • 💾 Auto-save - Automatic debounced saving to backend
  • 🎨 Rich Text Editor - Bold, italic, underline, lists
  • 🎨 Color Themes - Multiple color options
  • 📦 Minimize/Restore - Collapse notes to save space
  • 👁️ Hide/Show - Toggle note visibility
  • 📱 Responsive - Works on all screen sizes
  • 🔌 Backend Agnostic - Customizable API adapter
  • Zustand State Management - Efficient and performant
  • 🎭 Fully Typed - Complete TypeScript support

📦 Installation

Prerequisites

npm install zustand lucide-react
# or
yarn add zustand lucide-react
# or
pnpm add zustand lucide-react

Copy Files

Copy the following files to your project:

reusable-components/sticky-notes/
├── components/
│ ├── StickyNotesManager.tsx
│ └── StickyNoteComponent.tsx
├── store/
│ └── useStickyNotesStore.ts
├── adapters/
│ ├── apiAdapter.ts
│ └── defaultApiAdapter.ts
├── types/
│ └── index.ts
└── hooks/
└── useStickyNotes.ts

🚀 Quick Start

1. Configure API Adapter

Create your API adapter implementing the StickyNotesApiAdapter interface:

// src/adapters/myStickyNotesAdapter.ts
import { StickyNotesApiAdapter, StickyNote } from 'reusable-components/sticky-notes';

export const myStickyNotesAdapter: StickyNotesApiAdapter = {
async fetchNotes() {
const response = await fetch('/api/my-notes');
const data = await response.json();
return data.notes; // Return array of notes
},

async createNote(note: Partial<StickyNote>) {
const response = await fetch('/api/my-notes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(note),
});
const data = await response.json();
return data.note;
},

async updateNote(id: string, updates: Partial<StickyNote>) {
const response = await fetch(`/api/my-notes/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates),
});
const data = await response.json();
return data.note;
},

async deleteNote(id: string) {
await fetch(`/api/my-notes/${id}`, { method: 'DELETE' });
},

// Optional: Implement getAuthToken if your API requires authentication
getAuthToken: () => localStorage.getItem('token'),
};

2. Initialize Store with Your Adapter

// src/App.tsx or main entry point
import { initializeStickyNotesStore } from 'reusable-components/sticky-notes';
import { myStickyNotesAdapter } from './adapters/myStickyNotesAdapter';

// Initialize once at app startup
initializeStickyNotesStore(myStickyNotesAdapter);

3. Add to Your Application

// src/App.tsx
import { StickyNotesManager } from 'reusable-components/sticky-notes';

function App() {
return (
<div className="app">
{/* Your app content */}

{/* Sticky notes overlay */}
<StickyNotesManager />
</div>
);
}

4. Add Controls (Optional)

Use the provided hook to add controls anywhere:

import { useStickyNotes } from 'reusable-components/sticky-notes';

function MyControls() {
const { addNote, toggleVisibility, notes, isVisible } = useStickyNotes();

return (
<div>
<button onClick={addNote}>Add Note</button>
<button onClick={toggleVisibility}>
{isVisible ? 'Hide' : 'Show'} Notes ({notes.length})
</button>
</div>
);
}

🔧 Configuration

Custom Colors

Edit the colors array in the store:

const colors = [
'#fef3c7', // yellow
'#fde68a', // amber
'#fed7aa', // orange
// Add your custom colors...
];

Auto-save Timing

Adjust debounce times in useStickyNotesStore.ts:

const AUTO_SAVE_DELAY = 3000; // 3 seconds (default)
const NEW_NOTE_SAVE_DELAY = 1500; // 1.5 seconds for new notes

📖 API Reference

StickyNotesApiAdapter Interface

interface StickyNotesApiAdapter {
fetchNotes: () => Promise<StickyNote[]>;
createNote: (note: Partial<StickyNote>) => Promise<StickyNote>;
updateNote: (id: string, updates: Partial<StickyNote>) => Promise<StickyNote>;
deleteNote: (id: string) => Promise<void>;
getAuthToken?: () => string | null;
}

StickyNote Type

interface StickyNote {
id: string;
title: string;
content: string; // Rich HTML content
position: { x: number; y: number };
size: { width: number; height: number };
isMinimized: boolean;
isVisible: boolean;
color: string;
createdAt: string;
updatedAt: string;
isDirty?: boolean; // Has unsaved changes
isSaving?: boolean; // Currently saving
isUnsaved?: boolean; // Never been saved to server
}

useStickyNotes Hook

const {
notes, // Array of all notes
isVisible, // Global visibility state
autoSaveEnabled, // Auto-save toggle state
isLoading, // Loading state
error, // Error message
addNote, // () => void - Create new note
updateNote, // (id, updates) => void
updateTitle, // (id, title) => void
deleteNote, // (id) => Promise<void>
saveNote, // (id) => Promise<void>
saveAllNotes, // () => Promise<void>
toggleVisibility, // () => void - Show/hide all
hideNote, // (id) => void
showNote, // (id) => void
minimizeNote, // (id) => void
restoreNote, // (id) => void
toggleAutoSave, // () => void
loadNotesFromServer,// () => Promise<void>
} = useStickyNotes();

🎨 Styling Requirements

This component uses Tailwind CSS. Ensure your project has:

// tailwind.config.js
module.exports = {
content: [
// ... your content paths
'./reusable-components/**/*.{js,ts,jsx,tsx}',
],
// ... rest of config
}

🔌 Backend API Requirements

Your backend should implement these endpoints:

GET    /api/notes           - Fetch all notes
POST /api/notes - Create new note
PUT /api/notes/:id - Update note
DELETE /api/notes/:id - Delete note

Response Format Examples:

// GET /api/notes
{
"success": true,
"data": [
{
"id": "note-123",
"title": "My Note",
"content": "<p>Note content with <strong>rich</strong> text</p>",
"position": { "x": 100, "y": 100 },
"size": { "width": 250, "height": 200 },
"isMinimized": false,
"color": "#fef3c7",
"createdAt": "2025-01-01T00:00:00Z",
"updatedAt": "2025-01-01T00:00:00Z"
}
]
}

// POST /api/notes (Create)
// PUT /api/notes/:id (Update)
{
"success": true,
"data": {
"id": "note-123",
// ... note fields
}
}

🛠️ Advanced Usage

Manual Save Control

const { saveNote, notes } = useStickyNotes();

// Save specific note
await saveNote('note-id');

// Save all dirty notes
await Promise.all(
notes.filter(n => n.isDirty).map(n => saveNote(n.id))
);

Custom Event Handling

import { useStickyNotesStore } from 'reusable-components/sticky-notes';

// Subscribe to store changes
const unsubscribe = useStickyNotesStore.subscribe(
(state) => console.log('Notes updated:', state.notes.length)
);

// Cleanup when done
unsubscribe();

Keyboard Shortcuts

Built-in shortcuts in note content:

  • Ctrl/Cmd + B - Bold
  • Ctrl/Cmd + I - Italic
  • Ctrl/Cmd + U - Underline
  • Double-click title to edit
  • Enter or Escape to finish title edit

🐛 Troubleshooting

Notes disappear after saving

  • Check API response format matches expected structure
  • Ensure server returns the note with an id field
  • Check browser console for errors

Auto-save not working

  • Verify autoSaveEnabled is true
  • Check that your API adapter is properly configured
  • Look for network errors in browser console

Drag/resize issues

  • Ensure parent container doesn't have conflicting event handlers
  • Check z-index conflicts with other elements

📄 License

MIT - Feel free to use in your projects!

🤝 Contributing

Contributions welcome! This is a standalone component designed to be easily integrated into any React + TypeScript project.